package com.shm.leetcode;

import java.util.Stack;

/**
 * 331. 验证二叉树的前序序列化
 * 序列化二叉树的一种方法是使用前序遍历。当我们遇到一个非空节点时，我们可以记录下这个节点的值。如果它是一个空节点，我们可以使用一个标记值记录，例如 #。
 *
 *      _9_
 *     /   \
 *    3     2
 *   / \   / \
 *  4   1  #  6
 * / \ / \   / \
 * # # # #   # #
 * 例如，上面的二叉树可以被序列化为字符串 "9,3,4,#,#,1,#,#,2,#,6,#,#"，其中 # 代表一个空节点。
 *
 * 给定一串以逗号分隔的序列，验证它是否是正确的二叉树的前序序列化。编写一个在不重构树的条件下的可行算法。
 *
 * 每个以逗号分隔的字符或为一个整数或为一个表示 null 指针的 '#' 。
 *
 * 你可以认为输入格式总是有效的，例如它永远不会包含两个连续的逗号，比如 "1,,3" 。
 *
 * 示例 1:
 *
 * 输入: "9,3,4,#,#,1,#,#,2,#,6,#,#"
 * 输出: true
 * 示例 2:
 *
 * 输入: "1,#"
 * 输出: false
 * 示例 3:
 *
 * 输入: "9,#,#,1"
 * 输出: false
 * @author SHM
 */
public class IsValidSerialization {
    /**
     * 方法一：栈
     * 我们可以定义一个概念，叫做槽位。一个槽位可以被看作「当前二叉树中正在等待被节点填充」的那些位置。
     *
     * 二叉树的建立也伴随着槽位数量的变化。每当遇到一个节点时：
     *
     * 如果遇到了空节点，则要消耗一个槽位；
     *
     * 如果遇到了非空节点，则除了消耗一个槽位外，还要再补充两个槽位。
     *
     * 此外，还需要将根节点作为特殊情况处理。
     *
     *
     *
     * 我们使用栈来维护槽位的变化。栈中的每个元素，代表了对应节点处剩余槽位的数量，而栈顶元素就对应着下一步可用的槽位数量。当遇到空节点时，仅将栈顶元素减 11；当遇到非空节点时，将栈顶元素减 11 后，再向栈中压入一个 22。无论何时，如果栈顶元素变为 00，就立刻将栈顶弹出。
     *
     * 遍历结束后，若栈为空，说明没有待填充的槽位，因此是一个合法序列；否则若栈不为空，则序列不合法。此外，在遍历的过程中，若槽位数量不足，则序列不合法。
     *
     * 复杂度分析
     *
     * 时间复杂度：O(n)O(n)，其中 nn 为字符串的长度。我们每个字符只遍历一次，同时每个字符对应的操作都是常数时间的。
     *
     * 空间复杂度：O(n)O(n)。此为栈所需要使用的空间。
     *
     * 作者：LeetCode-Solution
     * 链接：https://leetcode-cn.com/problems/verify-preorder-serialization-of-a-binary-tree/solution/yan-zheng-er-cha-shu-de-qian-xu-xu-lie-h-jghn/
     * @param preorder
     * @return
     */
    public boolean isValidSerialization(String preorder) {
        Stack<Integer> ss = new Stack<>();
        String[] s = preorder.split(",");
        int n = s.length;

        ss.push(1);
        for(int i=0;i<n;i++){
            if(ss.isEmpty()){
                return false;
            }

            int k = ss.pop()-1;
            if(k>0){
                ss.push(k);
            }

            if(!"#".equals(s[i])){
                ss.push(2);
            }

        }

        return ss.isEmpty();
    }

    /**
     * 方法二：计数
     * 能否将方法一的空间复杂度优化至 O(1)O(1) 呢？
     *
     * 回顾方法一的逻辑，如果把栈中元素看成一个整体，即所有剩余槽位的数量，也能维护槽位的变化。
     *
     * 因此，我们可以只维护一个计数器，代表栈中所有元素之和，其余的操作逻辑均可以保持不变。
     *
     * 复杂度分析
     *
     * 时间复杂度：O(n)O(n)，其中 nn 为字符串的长度。我们每个字符只遍历一次，同时每个字符对应的操作都是常数时间的。
     *
     * 空间复杂度：O(1)O(1)。
     *
     * 作者：LeetCode-Solution
     * 链接：https://leetcode-cn.com/problems/verify-preorder-serialization-of-a-binary-tree/solution/yan-zheng-er-cha-shu-de-qian-xu-xu-lie-h-jghn/
     * @param preorder
     * @return
     */
    public boolean isValidSerialization_2(String preorder) {
        int cnt = 1;
        String[] split = preorder.split(",");
        for (int i = 0; i < split.length; i++) {
            if(cnt==0){
                return false;
            }
            cnt--;
            if(!"#".equals(split[i])){
                cnt+=2;
            }

        }
        return cnt==0;
    }
}
